package deployr.core;

import static deployr.core.Files.*;
import static java.text.MessageFormat.*;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import static deployr.core.Streams.*;

public class ZipPartitioned extends CopyOrZip {

	private Iterable<String> files = new LinkedList<String>();

	private final String root;

	private ZipListener zipListener = new ZipListener();

	private Checksum checksum = new Checksum();

	private long currentSize;
	private int currentChunkIndex;
	private final long MEGABYTE = 1024 * 1024;
	private final long MAX_FILE_SIZE = 1 * MEGABYTE;

	private ZipOutputStream out;

	public ZipPartitioned(String root, Iterable<String> files) {
		this.root = root;
		this.files = files;
	}

	public ZipPartitioned(String root, Checksum checksum) {
		this.root = root;
		this.checksum = checksum;
	}

	private boolean thereArentFiles() {
		return checksum.isEmpty() && !files.iterator().hasNext();
	}

	private boolean needContinue(Iterator<String> scanning) {
		return scanning.hasNext();
	}

	public String filename(String fullPath) {
		int sep = fullPath.lastIndexOf(File.separator);
		return fullPath.substring(sep + 1);
	}

	public void zip(String target) {
		if (thereArentFiles())
			return;

		beforeCopy(target);
		copy(target, files.iterator());
		copy(target, checksum.iterator());
		afterCopy();
	}

	private void afterCopy() {
		close(out);
	}

	private void beforeCopy(String target) {
		createOuput(target);
	}

	private void copy(String target, Iterator<String> copyingFiles) {
		while (needContinue(copyingFiles)) {
			copyTo(target, copyingFiles.next());
		}
	}

	private void copyTo(String target, String copyingFile) {
		if (isDirectory(copyingFile, root))
			return;
		File inputFile = copyByPathOrHash(root, copyingFile);
		File outputFile = makeOutputFile(target, copyingFile);
		doCopy(target, inputFile, outputFile);
		onCopying(outputFile);
	}

	private void doCopy(String target, File inputFile, File outputFile) {

		ZipEntry zipEntry;
		FileInputStream in;

		try {
			in = inputStream(inputFile);
			zipEntry = openEntry(target, outputFile.getAbsolutePath());
			copy(in);
			close(in);
			closeEntry(zipEntry);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private File makeOutputFile(String target, String copyingFile) {
		File outputFile = new File(target, copyingFile);
		// outputFile.getParentFile().mkdirs();
		return outputFile;
	}

	private File copyByPathOrHash(String root, String copyingFile) {
		File fileByPath = new File(root, copyingFile);
		if (fileByPath.exists())
			return fileByPath;
		return new File(root, checksum.hash(copyingFile));
	}
	
	private void onCopying(File outputFile) {
		if (null != zipListener)
			zipListener.addZip(outputFile.getAbsolutePath());
	}
	
	public void attach(ZipListener zipListener) {
		this.zipListener = zipListener;
	}

	private void addEntry(String target, ZipEntry entry) throws IOException {
		long entrySize = entry.getCompressedSize();
		if ((currentSize + entrySize) > MAX_FILE_SIZE) {
			close(out);
			createOuput(target);
		}
		out.putNextEntry(entry);
	}

	private void close(FileInputStream in) throws IOException {
		in.close();
	}

	private void close(ZipOutputStream out) {
		try {
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void closeEntry(ZipEntry zipEntry) throws IOException {
		out.closeEntry();
		long entrySize = zipEntry.getCompressedSize();
		currentSize += entrySize;
	}

	private void copy(FileInputStream in) throws IOException {
		byte[] buf = new byte[8192];
		int len;
		while ((len = in.read(buf)) > 0) {
			out.write(buf, 0, len);
		}
	}

	private void createOuput(String absolutePath) {
		currentChunkIndex++;
		currentSize = 0;
		String partName = partName(absolutePath);
		zipListener.addZip(partName);
		try {
			out = new ZipOutputStream(new FileOutputStream(partName));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private ZipEntry openEntry(String target, String relativePath) throws IOException {
		ZipEntry zipEntry = new ZipEntry(relativePath);
		addEntry(target, zipEntry);
		return zipEntry;
	}

	private String partName(String absolutePath) {
		return format("{0}.part.{1}.{2}", absolutePath, currentChunkIndex, "zip");
	}

}